﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Diagnostics;

namespace Dycox
{
    //[TypeConverter(typeof(EnumListItemConverter))]
    public class EnumListItem : IListItem
    {
        public EnumListItem(object value)
            : this(value, GetEnumDescription(value) ?? value.ToString())
        {
        }

        public EnumListItem(object value, string text)
        {
            this._value = value;
            this._text = text;
        }

        private string _text;

        public string Text
        {
            get { return _text; }
        }


        private object _value;

        public object Value
        {
            get { return _value; }
        }

        public static string GetEnumDescription(object value)
        {
            if (value == null)
                throw new ArgumentNullException("value");
            Type type = value.GetType();
            if (!type.IsEnum)
                throw new ArgumentException("The given value is not a Enum type.");

            var field = type.GetField(value.ToString(), System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public);
            if (field == null)
                throw new ArgumentException("Enum member " + value + " is not defined.");

            DescriptionAttribute da = (DescriptionAttribute)Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute));
            if (da != null) return da.Description;
            else return null;
        }

        public static EnumListItem[] FromEnum<T>()
        {
            return FromEnum(typeof(T), (System.Collections.ICollection)null);
        }

        public static EnumListItem[] FromEnum<T>(params T[] exclusives)
        {
            return FromEnum(typeof(T), exclusives);
        }

        public static EnumListItem[] FromEnum(Type enumType)
        {
            return FromEnum(enumType, (System.Collections.ICollection)null);
        }

        private static EnumListItem[] FromEnum(Type enumType, System.Collections.ICollection exclusives)
        {
            if (enumType == null)
                throw new ArgumentNullException("enumType");
            if (!enumType.IsEnum)
                throw new ArgumentException(enumType.FullName + " is not a Enum type.");

            Array values = Enum.GetValues(enumType);
            List<EnumListItem> list = new List<EnumListItem>(values.Length);

            foreach (object value in values)
            {

                if (exclusives != null && exclusives.Count > 0)
                {
                    bool found = false;
                    foreach (object v in exclusives)
                        if (object.Equals(v, value))
                        {
                            found = true;
                            break;
                        }

                    if (found) continue;
                }

                System.Reflection.FieldInfo field = enumType.GetField(value.ToString(), System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);

                BrowsableAttribute ba = (BrowsableAttribute)Attribute.GetCustomAttribute(
                    field, typeof(BrowsableAttribute));

                if (ba == null || ba.Browsable)
                {
                    string text;
                    DescriptionAttribute attr = Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) as DescriptionAttribute;

                    if (attr != null)
                        text = attr.Description;
                    else
                        text = field.Name;

                    list.Add(new EnumListItem(value, text));
                }
            }

            return list.ToArray();
        }

        //private static EnumListItem[] FromEnum(Type enumType, System.Collections.ICollection exclusives)
        //{
        //    if (enumType == null)
        //        throw new ArgumentNullException("enumType");
        //    if (!enumType.IsEnum)
        //        throw new ArgumentException(enumType.FullName + " is not a Enum type.");

        //    List<EnumListItem> list = new List<EnumListItem>();

        //    foreach (var field in enumType.GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static))
        //    {
        //        BrowsableAttribute ba = (BrowsableAttribute)Attribute.GetCustomAttribute(field, typeof(BrowsableAttribute));
        //        if (ba == null || ba.Browsable)
        //        {
        //            object value = field.GetValue(null);

        //            if (exclusives != null && exclusives.Count > 0)
        //            {
        //                bool found = false;
        //                foreach (object v in exclusives)
        //                    if (object.Equals(v, value))
        //                    {
        //                        found = true;
        //                        break;
        //                    }

        //                if (found) continue;
        //            }

        //            string text;
        //            DescriptionAttribute attr = Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) as DescriptionAttribute;

        //            if (attr != null)
        //                text = attr.Description;
        //            else
        //                text = field.Name;

        //            list.Add(new EnumListItem(value, text));
        //        }
        //    }

        //    return list.ToArray();
        //}

        // override object.Equals
        public override bool Equals(object obj)
        {
            //       
            // See the full list of guidelines at
            //   http://go.microsoft.com/fwlink/?LinkID=85237  
            // and also the guidance for operator== at
            //   http://go.microsoft.com/fwlink/?LinkId=85238
            //

            if (obj == null || GetType() != obj.GetType())
            {
                return false;
            }

            return object.Equals(((EnumListItem)obj)._value, _value);
        }

        // override object.GetHashCode
        public override int GetHashCode()
        {
            return _value.GetHashCode();
        }
    }

    sealed class EnumListItemConverter : Linq.ListItemConverter
    {
        Type _type;
        Dictionary<string, object> _descriptionLookup;
        bool _isArray;
        bool _isNullable;

        public EnumListItemConverter(Type type)
        {
            if (type.IsArray)
            {
                _isArray = true;
                type = type.GetElementType();
            }
            else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
            {
                type = Nullable.GetUnderlyingType(type);
                _isNullable = true;
            }

            _type = type;
        }

        private string GetEnumDescription(object value)
        {
            EnsureLookup();

            foreach (var pair in _descriptionLookup)
            {
                if (pair.Value.Equals(value)) return pair.Key;
            }

            return null;
        }

        private object GetEnumValueFromDescription(string description)
        {
            EnsureLookup();

            object v;

            if (!string.IsNullOrWhiteSpace(description) && _descriptionLookup.TryGetValue(description, out v))
                return v;
            else
                return null;
        }

        private object ParseEnum(string text)
        {
            if (!string.IsNullOrWhiteSpace(text))
            {
                object value = GetEnumValueFromDescription(text);
                if (value == null)
                {
                    try
                    {
                        value = Enum.Parse(_type, text, true);
                    }
                    catch (ArgumentException)
                    {
                    }
                }

                return value;
            }
            else
                return null;
        }

        private void EnsureLookup()
        {
            if (_descriptionLookup == null)
            {
                _descriptionLookup = new Dictionary<string, object>(StringComparer.InvariantCultureIgnoreCase);

                foreach (var field in _type.GetFields(System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public))
                {
                    var attr = (DescriptionAttribute)Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute));
                    if (attr != null)
                        _descriptionLookup[attr.Description] = Enum.Parse(_type, field.Name);
                }
            }
        }

        public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
        {
            return destinationType.IsEnum || destinationType == typeof(int) || destinationType == typeof(long) || base.CanConvertTo(context, destinationType);
        }

        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
        {
            return sourceType.IsEnum || sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
        }

        public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
        {
            if (value != null)
            {
                Type type = value.GetType();

                if (type.IsEnum)
                {
                    return new EnumListItem(value);
                }
                else if (type == typeof(string))
                {
                    object v = ParseEnum((string)value);
                    return v;
                }
            }
            return base.ConvertFrom(context, culture, value);
        }

        public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
        {
            EnumListItem item = value as EnumListItem;
            if (item == null && value != null && value.GetType().IsEnum)
                item = new EnumListItem(value);

            if (item != null)
            {
                if (destinationType == item.Value.GetType())
                    return item.Value;

                if (destinationType == typeof(string))
                    return item.Text;

                Int64 num = Convert.ToInt64(item.Value);
                if (destinationType.IsEnum)
                    return Enum.ToObject(destinationType, num);

                return Convert.ChangeType(num, destinationType);

            }
            return base.ConvertTo(context, culture, value, destinationType);
        }

        public override IListItem[] GetStandardItems(ITypeDescriptorContext context)
        {
            if (context != null)
            {
                return EnumListItem.FromEnum(_type);
            }

            return base.GetStandardItems(context);
        }
    }
}
